﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Media.IngestClient
{
    //http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx

    internal sealed class AsyncLock
    {
        private readonly AsyncSemaphore m_semaphore;
        private readonly Task<Releaser> m_releaser;

        public AsyncLock()
        {
            m_semaphore = new AsyncSemaphore(1);
#if NETFX_CORE
            m_releaser = Task.FromResult(new Releaser(this));
#else
            m_releaser = TaskEx.FromResult(new Releaser(this));
#endif
        }

        public Task<Releaser> LockAsync(CancellationToken c)
        {
            var wait = m_semaphore.WaitAsync();
            return wait.IsCompleted
                ? m_releaser
#if NETFX_CORE
                : wait.ContinueWith((_, state) => new Releaser((AsyncLock)state),
                    this, c,
                    TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
#else
                : wait.ContinueWith((_) => new Releaser(this),
                    c,
                    TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
#endif
        }

        public AsyncSemaphore Semaphore { get { return m_semaphore; } }
    }

    internal struct Releaser : IDisposable
    {
        private readonly AsyncLock m_toRelease;

        internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; }

        public void Dispose()
        {
            if (m_toRelease != null)
                m_toRelease.Semaphore.Release();
        } 

    }

    internal sealed class AsyncSemaphore
    {
#if NETFX_CORE
        private readonly static Task s_completed = Task.FromResult(true);
#else
        private readonly static Task s_completed = TaskEx.FromResult(true);
#endif
        private readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>();
        private int m_currentCount;

        public AsyncSemaphore(int initialCount)
        {
            if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount");
            m_currentCount = initialCount;
        }

        public Task WaitAsync()
        {
            lock (m_waiters)
            {
                if (m_currentCount > 0)
                {
                    --m_currentCount;
                    return s_completed;
                }
                else
                {
                    var waiter = new TaskCompletionSource<bool>();
                    m_waiters.Enqueue(waiter);
                    return waiter.Task;
                }
            }
        }

        public void Release()
        {
            TaskCompletionSource<bool> toRelease = null;
            lock (m_waiters)
            {
                if (m_waiters.Count > 0)
                    toRelease = m_waiters.Dequeue();
                else
                    ++m_currentCount;
            }
            if (toRelease != null)
                toRelease.SetResult(true);
        }
    }
}
